home *** CD-ROM | disk | FTP | other *** search
- WGT Graphics Tutorial #3
- Topic: Texture Mapped Polygons
- By Chris Egerter
- December 8, 1994
- Contact me at: chris.egerter@homebase.com
- Compuserve: 75242,2411
-
- Introduction
- ------------
- This series of tutorials describes a method of drawing filled polygons
- using 3 rendering techniques: Solid, Gouraud shading, and texture mapping.
-
- The code in this tutorial was written in Turbo C++ but can be ported to
- other graphics libraries and operating systems. I did not use the
- WGT functions in this one, so the wgtgfx.c file contains a few routines
- which are needed for the demos. I have decided to explain the method
- used for these routines since I had to discover them on my own, and
- think you can learn from the code.
-
-
- 1.0 - From Gouraud to Texture
- -----------------------------
- If you haven't read the previous tutorials, please read them first since
- they provide the basic ideas for filling polygons. In the last issue I
- mentioned that texture mapping is only slightly different from shading.
- This may not seem very obvious to you, so I'll explain.
-
- When we scan converted the Gouraud shaded edges, we kept track of the
- starting and ending color on each horizontal line. Texture mapping is
- the same, only we need to keep track of X and Y coordinates instead of
- a color. In place of a start and end color, we can get a (x,y) coordinate
- at each end of the line. Let's modify our point structure to store
- coordinates:
-
- typedef struct
- {
- int x,y;
- int sx, sy; /* Source coordinates from the texture image */
- } tpoint;
-
- sx and sy is a location on another image, which is associated with a
- vertex of the polygon. They are commonly called u and v coordiantes in
- graphics texts.
-
- When we converted the polygon into a list of x coordinates, we stored them
- into 4 arrays, startx and endx, startcol and endcol. With texture mapping
- we need to remove the color array, and replace it with startx,
- starty, endx and endy. I've decided to make this into a structure
- to keep things a bit cleaner.
-
- struct {
- int startx; /* The first X coord */
- int endx; /* The last X coord */
- int x1; /* First pixel coord out of texture */
- int y1;
- int x2; /* Last pixel coord out of texture */
- int y2;
- } texturepoint[200]; /* One for each scan line (320x200x256) */
-
- When the list is created, we will have two screen x coordinates,
- two x texture coordinates, and two y texture coordinates.
-
- The gpolyline routine becomes the tpolyline routine, which calculates
- the texture coordinates at the ends of each horizontal line. To do this,
- we use fixed point math in the same way as the Gouraud routine, only
- we step along the x and y at the same time.
-
- void tpolyline (int x1, int y1, int tx1, int ty1, int x2, int y2,
- int tx2, int ty2)
- /* Calculates the coordinates of a line given two
- vertices, (x1,y1) with texture coordinates (tx1, ty1), and
- (x2, y2) with texture coordinates (tx2,ty2).
-
- We will use fixed point math to speed things up.
- The x coordinate is multiplied by 256 and for each row,
- a constant m is added to x. This is a simplified version
- of a line algorithm because we only have to store 1 x coordinate
- for every y coordinate.
-
- The texture coordinates increase by a step value based on the
- number of pixels between the texture coordinates and the distance between
- the screen y coordinates.
-
- */
- {
- int tmp,y;
- long x,m;
-
- long xcoord, xstep; /* X texture coordinate, and step value */
- long ycoord, ystep; /* Y texture coordinate, and step value */
-
- if (y2 != y1) /* This isn't a horizontal line */
- {
- if (y2 < y1) /* Make sure y2 is greater than y1 */
- {
- tmp = y1; /* Swap the y coordinate */
- y1 = y2;
- y2 = tmp;
-
- tmp = x1; /* Swap the corresponding x coordinates */
- x1 = x2;
- x2 = tmp;
-
- tmp = tx1; /* Swap the corresponding x values */
- tx1 = tx2;
- tx2 = tmp;
-
- tmp = ty1; /* Swap the corresponding y values */
- ty1 = ty2;
- ty2 = tmp;
- }
-
- x = (long)x1<<8; /* Multiply by 256 */
-
- m = ((long)(x2 - x1)<<8) / ((long)(y2 - y1));
- /* m is the fractional amount to add to the x coordinate every row.
- m is equal to (delta x) / (delta y). In other words, the x coordinate
- has to change by (x2 - x1) columns in (y2 - y1) rows. */
-
- xcoord = (long)tx1 << 8; /* Initial x coord in 8.8 fixed point format */
- xstep = ((long)(tx2 - tx1) << 8) / ((long)(y2 - y1));
- /* Calculate the x step value */
-
- ycoord = (long)ty1 << 8; /* Initial y coord in 8.8 fixed point format */
- ystep = ((long)(ty2 - ty1) << 8) / ((long)(y2 - y1));
- /* Calculate the y step value */
-
- x += m; /* We ALWAYS skip the first point in every line. This is done */
- y1++; /* because we do not want to store the point where two lines
- meet, twice. This would result in a single point being drawn. */
-
- for (y = y1; y <= y2; y++) /* Go through each row */
- {
- if ((y >= 0) & (y < 200)) /* If the coordinate is on the screen */
- if (texturepoint[y].startx == -16000) /* Store the first coordinate */
- {
- texturepoint[y].startx = x >> 8; /* Store the first coordinate */
- texturepoint[y].x1 = xcoord >> 8;
- texturepoint[y].y1 = ycoord >> 8;
- }
- else
- {
- texturepoint[y].endx = x >> 8; /* Store the last coordinate */
- texturepoint[y].x2 = xcoord >> 8;
- texturepoint[y].y2 = ycoord >> 8;
- }
- x += m; /* Add our constant to x */
- xcoord += xstep; /* Add our x step value to the texture */
- ycoord += ystep; /* Add our y step value to the texture */
- }
- }
- }
-
-
- Next we have the actual texture mapping routine that calls the previous
- routine and draws the polygon.
-
-
- void texturedpoly (tpoint *vertexlist, int numvertex)
- /* Draws a textured polygon given an array of vertices. */
- {
- int i;
- tpoint *curpt,*nextpt;
- /* Two pointers to a vertex. These are used to connect to vertices
- together in when calling the gpolyline routine. */
-
- curpt = vertexlist; /* Set to the first vertex in the array */
- nextpt = vertexlist + 1; /* and to the second vertex */
-
- for (i = 0; i < 200; i++)
- {
- texturepoint[i].startx = -16000; /* Set up our impossible values */
- texturepoint[i].endx = -16000;
- }
-
- for (i = 1; i < numvertex; i++)
- {
- tpolyline (curpt->x, curpt->y, curpt->sx, curpt->sy,
- nextpt->x, nextpt->y, nextpt->sx, nextpt->sy);
- /* Calculate the edge of this line. */
-
- curpt += 1; /* Go to the next line */
- nextpt += 1;
- }
-
- nextpt = vertexlist; /* Now close the polygon by doing a line between
- the first and last vertex. */
- tpolyline (curpt->x, curpt->y, curpt->sx, curpt->sy,
- nextpt->x, nextpt->y, nextpt->sx, nextpt->sy);
-
- for (i = 0; i < 200; i++) /* Now draw the horizontal line list */
- if (texturepoint[i].startx != -16000)
- /* Indicates there is a line on this row */
- {
- if (texturepoint[i].endx == -16000)
- texturepoint[i].endx = texturepoint[i].startx;
- /* In case there was only one point found on this row */
- tmapline (texturepoint[i].startx,
- texturepoint[i].x1, texturepoint[i].y1,
- texturepoint[i].endx,
- texturepoint[i].x2, texturepoint[i].y2,
- i);
- /* Draw a texture mapped line between the two x coordinates,
- on the row i. */
- }
- }
-
-
- So far the code is just keeping track of the coordinates for each
- horizontal line. The real texture mapping code lies in the tmapline
- routine which draws it on the screen.
-
-
- 2.0 Size Restrictions
- ---------------------
- I will now place a restriction on our textures to enable some optimizations.
- The texture MUST have a width of 256, and has a maximum height of 256.
- The reason for this is you can store an X or Y texture coordinate in half
- of a register.
-
- This restriction isn't necessary. I also have code for texture mapping
- any size texture, but I feel the speed increase is well worth it and decided
- to present this method.
-
- 3.0 - TMAPLINE Pseudo-code
- --------------------------
- Our basic texture mapped line routine looks like this:
- Calculate the x step value
- Calculate the y step value
- Make a coordinate variable equal to the left endpoint's texture coordinate.
- For x = x1 to x2
- Read a pixel from the texture
- Put pixel on screen
- Add x step value to the texture coordinate
- Add y step value to the texture coordinate
- End for
-
-
- 4.0 Calculating the step values
- -------------------------------
- The tmapline routine needs to step along the x and y coordinate as we draw
- the line. To do this, we can calculate a step value similar to the
- technique described in the Gouraud shading tutorial.
-
- length = x2 - x1 + 1; /* Calculate the length of this line */
-
- deltax = tmapx2 - tmapx1 + 1; /* Find the delta values */
- deltay = tmapy2 - tmapy1 + 1;
-
- xincr = ((long)(deltax) << 8) / (long)length;
- yincr = ((long)(deltay) << 8) / (long)length;
-
- Now we have the values which are added to the texture coordinate
- for every pixel drawn.
-
-
- 5.0 Assembly Tricks
- -------------------
- Here's how I would set up the registers to hold the texture coordinates.
- This is the key to optimized texture mapping.
-
- EDX is a 32-bit register that will contain the texture coordinates.
- Since the x and y coordinates can go up to 256, they use 8 bits each.
- As well, we will use 8 bits for the fractional portion of our fixed point
- number.
-
- Y whole value Y fraction X whole value X fraction
- <--------------.--------------> <--------------.-------------->
- 1 2 3 4 5 6 7 8 9 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 3
- 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2
-
- As you can see, the register will hold two 8.8 fixed point numbers.
-
-
- ESI is used the same way, only it holds the step values.
-
- Y whole step Y frac. step X whole step X frac. step
- <--------------.--------------> <--------------.-------------->
- 1 2 3 4 5 6 7 8 9 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 3
- 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2
-
- To add the step values to the texture coordinates, we simply add
- ESI to EDX.
-
- The next step is to take the x and y whole value out of EDX and
- create a single offset into our texture image. To do this, we have
- to remove the fractional portions, multiply the y value by the texture's
- width, and add them together.
-
- First copy EDX into another register so we can work with it. I used EBX
- because it was the only one left. We want to access the high word of the
- register, so we must shift it right.
-
- mov ebx, edx /* Copy EDX into EBX for work */
- shr ebx, 16 /* BH now contains the y coordinate */
-
- By shifting it right 16 places, EBX now looks like this:
- Y whole value Y fraction
- <-----BH-------.------BL------>
- 1 2 3 4 5 6 7 8 9 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 3
- 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2
-
- Here's the trick. Since our texture image has a width of 256, and the
- y is in BH, it is already multiplied by the width! To create an offset,
- all we have left to do is add the x value. In other words, we have to put
- the x whole value into BL. EBX no longer contains the x value, so we'll
- get it from EDX. The x whole value is stored in DH.
-
- mov bl, dh /* Store the x value in BL */
-
- BX is now an offset into the texture image, between 0 and 65535.
- To get the pixel from the texture image, we use bx as the offset,
- and ds as the segment of the texture. This means the texture MUST be
- aligned to a segment. Having a texture fit perfectly within a segment
- enables you to have a repeating texture, where the source texture coordinates
- are much large than the texture itself. The values will wrap around at
- 256 and cause the texture to be shown more than once. You will see this
- effect in the first demo.
-
- mov al, ds:[bx] /* Get the color from the texture image */
-
- Finally we need to add the step values to the texture coordinates.
-
- add edx, esi /* Advance one pixel */
-
- You can then put the pixel on the visual screen, where the pointer to the
- screen is stored in es:di.
-
-
- 6.0 Other optimizations
- -----------------------
- Another optimization I used is drawing two pixels at once. You can
- accomplish this by storing the first pixel in ah, the second in al,
- and writing AX to the screen. This method requires a check to see if an odd
- pixel needs to be drawn.
-
-
- 7.0 The code
- ------------
- Whew. That was pretty complicated code. The same thing could have been done
- easily in C, but several key optimizations would not be possible.
- Here is the full texture mapping line routine:
-
- void tmapline (int x1, int tmapx1, int tmapy1, int x2, int tmapx2,
- int tmapy2, int y)
- /* Draws one scanline of the textured polygon.
- Where:
- x1 is the first x coordinate
- tmapx1 is the x coordinate on the texture relating to (x1,y)
- tmapy1 is the y coordinate on the texture relating to (x1,y)
- x2 is the second x coordinate
- tmapx2 is the x coordinate on the texture relating to (x2,y)
- tmapy2 is the y coordinate on the texture relating to (x2,y)
- y is the scanline to draw the line on
- */
- {
- long length;
- long deltax, deltay; /* Difference between the x and y coordinates */
-
- unsigned char far * dest; /* Ptr to the screen */
- unsigned char far * src; /* Ptr to texture image */
-
- int xincr; /* X offset into texture, amount to increase every pixel */
- int yincr; /* Y offset into texture, amount to increase every pixel */
-
- int xpos, ypos; /* Stores fractional part after clipping */
-
- int t; /* Used for swapping */
-
-
- if (x1 > x2) /* Swap all the coordinates so x1 < x2 */
- {
- t = x1; x1 = x2; x2 = t;
- t = tmapx1; tmapx1 = tmapx2; tmapx2 = t;
- t = tmapy1; tmapy1 = tmapy2; tmapy2 = t;
- }
-
- length = x2 - x1 + 1; /* Calculate the length of this line */
- if (length > 0)
- {
- deltax = tmapx2 - tmapx1 + 1; /* Find the delta values */
- deltay = tmapy2 - tmapy1 + 1;
-
- dest = abuf + y * 320 + x1; /* Make a pointer to the correct place in
- video memory */
- src = textureimage + tmapy1 * 256 + tmapx1;
- /* Make a pointer to the correct place on the texture map
- Assumes the image is 256 in width. */
-
- xincr = ((long)(deltax) << 8) / (long)length;
- yincr = ((long)(deltay) << 8) / (long)length;
-
- /* Calculate the step value for the x and y coordinates.
- For every pixel on the destination, the x coordinate on the texture
- will move xincr pixels. */
-
- xpos = tmapx1<<8;
- ypos = tmapy1<<8;
-
- asm {
- .386
- push ds
- cld
- mov cx, word ptr length /* Set length */
- shr cx, 1
- les di, dest /* Set destination ptr */
- lds si, src /* Set source ptr */
- mov dx, word ptr ypos /* Put the y in the low word */
- shl edx, 16 /* Move the y to the high word */
- mov dx, word ptr xpos /* Put the x in the low word */
-
- mov si, word ptr yincr /* Set up the increments the */
- shl esi, 16 /* same way */
- mov si, word ptr xincr
- /* Now to advance one pixel, we can add edx and esi together to
- advance the x and y at the same time, with the fractional
- portion automatically carrying at 256. */
-
- cmp cx, 0
- je onepixel
- }
- tlineloop:
- ;
- asm {
- mov ebx, edx
- shr ebx, 16 /* BH now contains the y coordinate */
- mov bl, dh /* Store the x value in BL, */
- /* BX is now an offset into the texture image,
- between 0 and 65535. */
- mov al, ds:[bx] /* Get the color from the texture image */
- add edx, esi /* Advance one pixel */
-
- mov ebx, edx /* Repeat the above, and get another pixel */
- shr ebx, 16
- mov bl, dh
- mov ah, ds:[bx]
- add edx, esi
-
- stosw /* Store a word to the destination */
- dec cx /* Decrease length */
- jnz tlineloop /* Repeat for all pixels */
- }
- onepixel:
- asm {
- mov cx, word ptr length
- and cx, 1
- jz tlinedone
-
- mov ebx, edx
- shr ebx, 16 /* BH now contains the y coordinate */
- mov bl, dh /* Store the x value in BL, */
- /* BX is now an offset into the texture image,
- between 0 and 65535. */
- mov al, ds:[bx] /* Get the color from the texture image */
- mov es:[di], al
-
- }
- tlinedone:
- asm {
- pop ds
- }
- }
- }
-
-
-
- 8.0 - What About Clipping?
- --------------------------
- Clipping can be performed similar to the way I used in the Gouraud
- shaded line routine. I will leave this for you to complete if you require
- it.
-
-
- 9.0 - Future Tutorials
- ----------------------
- The next tutorial will discuss using dirty rectangles to speed up your
- screen updates. If you have any requests for future topics, feel free to
- suggest them to me and I'll see what I can do.
-
-
-
- 10.0 - Where to get WGT 4.0
- ---------------------------
- You can download my graphics library from two places:
-
- FTP site:
- x2ftp.oulu.fi under /pub/msdos/programming/wgt/wgt4.zip
-
- BBS:
- Softnet BBS (519)-472-3661
- Located in London, Ontario, Canada.
- Login name: word up
- Password: graphics
-
- The files are located in file area #10.
-
-
-
-
-